package com.shopx.core.utils;


import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Reflector {
    private static Map<String, Field> FID2FIELD = new ConcurrentHashMap<String, Field>();
    private static Map<String, String[]> CLZ2FIELD = new ConcurrentHashMap<String, String[]>();
    private static Map<String, Method> KEY2MTH = new ConcurrentHashMap<String, Method>();
    private static final String CGLIB_FLAG = "$$EnhancerByCGLIB$$";

    public static boolean exists(String clz) {
        try {
            Class.forName(clz);
        } catch (ClassNotFoundException e) {
            return false;
        }
        return true;
    }

    public static final Class<?> getInterface(Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }
        Class<?> clz = obj.getClass();
        String name = clz.getSimpleName();
        if (name.indexOf(CGLIB_FLAG) != -1) {
            clz = clz.getSuperclass();
        }
        Class<?>[] intfs = clz.getInterfaces();
        if (intfs.length == 0) {
            return null;
        } else if (intfs.length == 1) {
            return intfs[0];
        } else {
            for (int i = 0, len = intfs.length; i < len; i++) {
                String name2 = intfs[i].getName();
                if (name2.startsWith("java.") || name2.startsWith("javax.")) {
                    continue;
                } else {
                    return intfs[i];
                }
            }
            return intfs[0];
        }
    }

    public static Object createArray(Class<?> clz, int len) {
        return Array.newInstance(clz, len);
    }

    public static Object create(String clz) {
        try {
            return create(Class.forName(clz));
        } catch (Throwable t) {
            throw new RuntimeException("Can't new " + clz, t);
        }
    }

    public static Object create(@SuppressWarnings("rawtypes") Class clz) {
        try {
            return clz.newInstance();
        } catch (Throwable t) {
            throw new RuntimeException("Can't new " + clz, t);
        }
    }

    public static Field[] getAllField(Class<?> clazz) {
        List<Field> lst = new ArrayList<Field>();
        while (clazz != null) {
            Field[] fds2 = clazz.getDeclaredFields();
            for (int i = 0; i < fds2.length; i++) {
                int mod = fds2[i].getModifiers();
                if (Modifier.isFinal(mod)) {
                    continue;
                }
                lst.add(fds2[i]);
            }
            clazz = clazz.getSuperclass();
        }
        return lst.toArray(new Field[lst.size()]);
    }

    public static String[] getAllFieldName(Object obj) {
        return getAllFieldName(obj.getClass());
    }

    public static String[] getAllFieldName(Class<?> clazz) {
        // check cache first
        String clzName = clazz.getName();
        String[] fields = CLZ2FIELD.get(clzName);
        if (fields != null) {
            return fields;
        }
        // lookup to Object
        Map<String, String> map = new HashMap<String, String>();
        List<String> lst = new ArrayList<String>();
        String prefix = "";
        while (clazz != null) {
            Field[] fds2 = clazz.getDeclaredFields();
            for (int i = 0; i < fds2.length; i++) {
                int mod = fds2[i].getModifiers();
                if (Modifier.isFinal(mod)) {
                    continue;
                }
                String field = fds2[i].getName();
                if (map.containsKey(field)) {
                    field = prefix + field;
                }
                lst.add(field);
                map.put(field, null);
                fds2[i].setAccessible(true);
                FID2FIELD.put(clzName + "@" + field, fds2[i]);
            }
            prefix = "p." + prefix;
            clazz = clazz.getSuperclass();
        }
        // fill cache
        fields = lst.toArray(new String[lst.size()]);
        CLZ2FIELD.put(clzName, fields);
        return fields;
    }

    public static Class<?> getFieldType(Class<?> clazz, String property) {
        String id = getFieldId(clazz, property);
        Field field = FID2FIELD.get(id);
        if (field == null) {
            if (!CLZ2FIELD.containsKey(clazz.getName())) {
                getAllFieldName(clazz);
            }
            field = FID2FIELD.get(id);
        }
        if (field == null) {
            // throw new RuntimeException(id + " not exists!");
            return null;
        } else {
            return field.getType();
        }
    }

    public static Method[] getAllMethod(Class<?> clazz) {
        return clazz.getDeclaredMethods();
    }

    public static Method getMethod(Class<?> clazz, String method) {
        String key = clazz.getName() + ":" + method;
        Method mth = KEY2MTH.get(key);
        if (mth == null) {
            // lookup from clazz
            Method[] mths = clazz.getDeclaredMethods();
            for (int i = 0; i < mths.length; i++) {
                Method tmp = mths[i];
                if (tmp.getName().equals(method)) {
                    if (mth == null) {
                        mth = tmp;
                    } else {
                        throw new RuntimeException("More than one method! " + clazz + ":" + method);
                    }
                }
            }
            // null or put in cache
            if (mth == null) {
                throw new RuntimeException("Method not found! " + clazz + ":" + method);
            } else {
                KEY2MTH.put(key, mth);
            }
        }

        return mth;
    }

    public static Method getMethod(Class<?> clazz, String method, int argsNum) {
        String key = clazz.getName() + ":" + method + ":" + argsNum;
        Method[] mths = clazz.getDeclaredMethods();
        for (int i = 0; i < mths.length; i++) {
            Method tmp = mths[i];
            if (tmp.getName().equalsIgnoreCase(method)) {
                Class<?>[] args2 = tmp.getParameterTypes();
                int now = args2 == null ? 0 : args2.length;
                if (now == argsNum) {
                    KEY2MTH.put(key, tmp);
                    return tmp;
                }
            }
        }
        return null;
    }

    public static void setValue(Object object, String property, Object value) {
        if (object == null) {
            return; // null target, return first
        }
        Class<?> clz = object.getClass();
        try {
            String key = clz.getName() + "@" + property;
            Field field = FID2FIELD.get(key);
            if (field == null) {
                getAllFieldName(object);
            }
            field = FID2FIELD.get(key);
            if (field == null) {
                throw new RuntimeException("Can't find field " + property);
            }
            field.set(object, value);
        } catch (Throwable t) {
            throw new RuntimeException("Fail to get " + object + " -> " + property, t);
        }
    }

    public static Object getValue(Object obj, String property) {
        if (obj == null) {
            return null;
        }
        Class<?> clz = obj.getClass();
        try {
            String key = clz.getName() + "@" + property;
            Field field = FID2FIELD.get(key);
            if (field == null) {
                int pos = property.indexOf('.');
                if (pos > 0) {
                    String p1 = property.substring(0, pos);
                    String p2 = property.substring(pos + 1);
                    return getValue(getValue(obj, p1), p2);
                }
                field = getField(clz, property);
                field.setAccessible(true);
                FID2FIELD.put(key, field);
            }
            return field.get(obj);
        } catch (Throwable t) {
            throw new RuntimeException("Fail to get " + obj + " -> " + property, t);
        }
    }

    private static Field getField(@SuppressWarnings("rawtypes") Class clz, String property) {
        while (clz != null) {
            try {
                return clz.getDeclaredField(property);
            } catch (NoSuchFieldException nfe) {
                clz = clz.getSuperclass();
            }
        }
        return getEnumField(property);
    }

    private static Field getEnumField(String property) {
        try {
            return Enum.class.getDeclaredField(property);
        } catch (Exception e) {
            throw new RuntimeException("Can't find filed " + property);
        }
    }

    private static String getFieldId(Class<?> clazz, String field) {
        return clazz.getName() + "@" + field;
    }
}
